﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

// TODO
// check if values xMin, xMax... are coherents
namespace DARE
{
    /// <summary>
    /// Implement an octree system.
    /// Minimum octree's depth is one.
    /// </summary>
    public class COctree : IPartitioner
    {

    #region fields

        private COctreeNode m_root;
        private bool m_cleanOnRemove;

        private int m_totalItems;

        private CRegularList m_regularList;
        private CBackToFrontList m_backToFrontList;
        private CFrontToBackList m_frontToBackList;
        private List<int> m_addedItemsIds = new List<int>();

        /// <summary>
        /// This is the size of the items's list when the octree is created.
        /// If more items than the size are added there will be a dynamic allocation.
        /// WARNING ! not working atm.
        /// </summary>
        private int m_initSize;

        // used to draw the bounding box
        private COctreeAABBModel m_aabbModel;

        private bool m_show;
        private bool m_showItems;
        private FillMode m_itemsFillMode;

        private float m_LODRange;

    #endregion

    #region properties

        /// <summary>
        /// List of IPartitioner items after the call of Process.
        /// The items are not ordered.
        /// </summary>
        public List<IPartitionerItem> Regular
        {
            get { return m_regularList.items; }
        }

        /// <summary>
        /// List of IPartitioner items ordered from back to front after the call of Process.
        /// </summary>
        public List<IPartitionerItem> BackToFront
        {
            get { return m_backToFrontList.items; }
        }

        /// <summary>
        /// List of IPartitioner items ordered from front to back after the call of Process.
        /// </summary>
        public List<IPartitionerItem> FrontToBack
        {
            get { return m_frontToBackList.items; }
        }
        
        /// <summary>
        /// Number of Regular items processed.
        /// Use "Regular" to get the list of items processed.
        /// </summary>
        public int RegularNumber
        {
            get { return m_regularList.itemsNumber; }
        }

        /// <summary>
        /// Number of back to front items processed.
        /// </summary>
        public int BackToFrontNumber
        {
            get { return m_backToFrontList.itemsNumber; }
        }

        /// <summary>
        /// Number of front to back items processed.
        /// </summary>
        public int FrontToBackNumber
        {
            get { return m_frontToBackList.itemsNumber; }
        }

        /// <summary>
        /// The root node of the octree is the node at the top, his size is the size of the octree.
        /// Get the root node.
        /// </summary>
        public COctreeNode Root
        {
            get { return m_root; }
        }

        /// <summary>
        /// Show or not the space partitionner.
        /// Get if the partitionner is shown.
        /// Set if the partitioner is shown.
        /// </summary>
        public bool Show
        {
            get { return m_show; }
            set { m_show = value; }
        }

        /// <summary>
        /// Show or not all the collisions view boxes of each items in the space partitionner.
        /// Works only if "Show" is true.
        /// Get if the items are shown.
        /// Set if the items are shown.
        /// </summary>
        public bool ShowItems
        {
            get { return m_showItems; }
            set { m_showItems = value; }
        }

        public FillMode ItemsFillMode
        {
            get { return m_itemsFillMode; }
            set { m_itemsFillMode = value; }
        }

        /// <summary>
        /// The LOD range modify the distances between two differents LOD values.
        /// Get the LOD range.
        /// Set the LOD range.
        /// </summary>
        public float LODRange
        {
            get { return m_LODRange; }
            set 
            {
                if (value >= 0)
                    m_LODRange = value;
            }
        }

    #endregion

    #region ctor

        /// <summary>
        /// Create an octree to store nodes.
        /// </summary>
        /// <param name="size">Size of the octree, it will be a cube centered in 0.</param>
        public COctree(float size) :
            this(-size / 2, size / 2, -size / 2, size / 2, -size / 2, size / 2, 5, 1)
        { }

        /// <summary>
        /// Create an octree to store nodes.
        /// </summary>
        /// <param name="size">Size of the octree, it will be a cube centered in 0.</param>
        /// <param name="maxItems">Maximum items in one branch.</param>
        public COctree(float size, int maxItems) :
            this(-size / 2, size / 2, -size / 2, size / 2, -size / 2, size / 2, maxItems, 1)
        { }

        /// <summary>
        /// Create an octree to store nodes.
        /// </summary>
        /// <param name="size">Size of the octree, it will be a cube centered in 0.</param>
        /// <param name="maxItems">Maximum items in one branch.</param>
        /// <param name="minDepth">Minimum depth of the octree.</param>
        public COctree(float size, int maxItems, int minDepth) :
            this(-size / 2, size / 2, -size / 2, size / 2, -size / 2, size / 2, maxItems, minDepth)
        { }

        /// <summary>
        /// Create the octree.
        /// Max items in one branch is set to 5 and minimum depth is 1.
        /// </summary>
        /// <param name="xMin">Minimum size x.</param>
        /// <param name="xMax">Maximum size x.</param>
        /// <param name="yMin">Minimum size y.</param>
        /// <param name="yMax">Maximum size y.</param>
        /// <param name="zMin">Minimum size z.</param>
        /// <param name="zMax">Maximum size z.</param>
        public COctree(float xMin, float xMax, float yMin, float yMax, float zMin, float zMax) :
            this(xMin, xMax, yMin, yMax, zMin, zMax, 5, 1)
        { }

        /// <summary>
        /// Create the octree.
        /// Minimum depth is set to 1.
        /// </summary>
        /// <param name="xMin">Minimum size x.</param>
        /// <param name="xMax">Maximum size x.</param>
        /// <param name="yMin">Minimum size y.</param>
        /// <param name="yMax">Maximun size y.</param>
        /// <param name="zMin">Minimum size z.</param>
        /// <param name="zMax">Maximum size z.</param>
        /// <param name="maxItems">Max items in one branch.</param>
        public COctree(float xMin, float xMax, float yMin, float yMax, float zMin, float zMax, int maxItems) :
            this(xMin, xMax, yMin, yMax, zMin, zMax, maxItems, 1)
        { }

        /// <summary>
        /// Create the octree.
        /// </summary>
        /// <param name="xMin">Minimum size x.</param>
        /// <param name="xMax">Maximum size x.</param>
        /// <param name="yMin">Minimum size y.</param>
        /// <param name="yMax">Maximum size y.</param>
        /// <param name="zMin">Minimum size z.</param>
        /// <param name="zMax">Maximum size z.</param>
        /// <param name="maxItems">Max items in one branch.</param>
        /// <param name="minDepth">Minimum depth of the octree.</param>
        public COctree(float xMin, float xMax, float yMin, float yMax, float zMin, float zMax, int maxItems, int minDepth)
        {
            m_initSize = 250;
            m_LODRange = 100;

            if (!(xMin < xMax || yMin < yMax || zMin < zMax))
                throw new Exception("Octree initialization failed: wrong dimensions, condition xMin < xMax || yMin < yMax || zMin < zMax is not true");
            m_show = false;
            m_showItems = true;
            m_itemsFillMode = FillMode.WireFrame;
            m_aabbModel = new COctreeAABBModel(new BoundingBox(new Vector3(xMin, yMin, zMin), new Vector3(xMax, yMax, zMax)), -1);
            m_regularList = new CRegularList(m_initSize);
            m_backToFrontList = new CBackToFrontList();
            m_frontToBackList = new CFrontToBackList();
            m_root = new COctreeNode(xMin, xMax, yMin, yMax, zMin, zMax, maxItems, minDepth, this);
        }

    #endregion

    #region methods

        /// <summary>
        /// Add an item in the process lists. Each octree item is added to the list by this method.
        /// </summary>
        /// <param name="camPos">Camera's position.</param>
        /// <param name="item">Partitionner item to add in the process lists.</param>
        public void AddProcessItem(Vector3 camPos, IPartitionerItem item)
        {
            if (m_addedItemsIds.Contains(item.Id))
                return;
            bool added = false;

            // add the item to the suited list
            switch (item.Order)
            {
                case EOrder.REGULAR:
                    added = m_regularList.AddItem(camPos, item);
                    break;
                case EOrder.BACKTOFRONT:
                    added = m_backToFrontList.AddItem(camPos, item);
                    break;
                case EOrder.FRONTTOBACK:
                    added = m_frontToBackList.AddItem(camPos, item);
                    break;

                default: throw new Exception("Unknown EOrder !");
            }
            // apply the LOD
            if (added)
            {
                m_addedItemsIds.Add(item.Id);
                int mul = 1;
                int i = 0;

                while (((float)(mul * mul) * m_LODRange < item.DistFromCam) &&
                    (i < 10))
                {
                    mul *= 2;
                    i += 1;
                }
                item.LOD = i;
            }
        }

        /// <summary>
        /// Draw the space partitionner in 3d is Show is set to true.
        /// Used to debug.
        /// </summary>
        public void Draw()
        {
            if (m_show)
            {
                m_aabbModel.Draw();
                m_root.Draw();
            }
        }

        // WARNING !!!!
        // if an object is at the center and call parent... and the parent is the COtree, then AddItem can loop ?

        /// <summary>
        /// Add in item in the space partitionner.
        /// </summary>
        /// <param name="item">Item to add.</param>
        /// <returns>Succes or failure.</returns>
        public bool AddItem(IPartitionerItem item)
        {
            if ((item.Position.X < m_root.AABB.Min.X || item.Position.X > m_root.AABB.Max.X) ||
                (item.Position.Y < m_root.AABB.Min.Y || item.Position.Y > m_root.AABB.Max.Y) ||
                (item.Position.Z < m_root.AABB.Min.Z || item.Position.Z > m_root.AABB.Max.Z))
                throw new Exception("Octree AddItem error: item out of the world size !");
            return m_root.AddItem(item);
        }

        /// <summary>
        /// Remove an item from the partitionner.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool RemoveItem(IPartitionerItem item)
        {
            return m_root.RemoveItem(item);
        }

        /// <summary>
        /// Remove all the items from the space partitionner.
        /// </summary>
        /// <returns>Success if at least one was removed, or failure.</returns>
        public bool Clear()
        {
            m_regularList.items.Capacity = m_initSize;
            return m_root.Clear();
        }

        /// <summary>
        /// Process the items inside or intersected by all rendering cameras frustrum.
        /// The processed items are accessible throught the Regular / BackToFront and FrontToBack lists.
        /// </summary>
        /// <param name="cams">Rendering cameras list</param>
        public void Process(List<CCamera> cams)
        {
            m_regularList.Clear();
            m_backToFrontList.Clear();
            m_frontToBackList.Clear();
            m_addedItemsIds.Clear();
            m_root.Process(cams);
        }


        /// <summary>
        /// Process the item closest to a point.
        /// </summary>
        /// <param name="pos">Position where to surch the nearest item.</param>
        public void Process(Vector3 pos)
        {
            m_regularList.Clear();
            m_backToFrontList.Clear();
            m_frontToBackList.Clear();
            m_addedItemsIds.Clear();
            m_root.Process(pos);
        }

        /// <summary>
        /// Proces the items inside or intersected by the frustrum.
        /// The processed items are accessible throught the Regular / BackToFront / FrontToBack lists.
        /// </summary>
        /// <param name="camPos">Camera position.</param>
        /// <param name="collider">Bounding frustrum.</param>
        public void Process(Vector3 camPos, BoundingFrustum collider)
        {
            m_regularList.Clear();
            m_backToFrontList.Clear();
            m_frontToBackList.Clear();
            m_addedItemsIds.Clear();
            m_root.Process(camPos, collider);
        }

        /// <summary>
        /// Process the items inside or intersected by the given box.
        /// The processed items are accessible throught the Regular / BackToFront and FrontToBack lists.
        /// </summary>
        /// <param name="camPos">Camera position.</param>
        /// <param name="collider">Axis aligned bounding box.</param>
        public void Process(Vector3 camPos, BoundingBox collider)
        {
            m_regularList.Clear();
            m_backToFrontList.Clear();
            m_frontToBackList.Clear();
            m_addedItemsIds.Clear();
            m_root.Process(camPos, collider);
        }

        /// <summary>
        /// Process the items inside or intersected by a sphere.
        /// The processed items are accessible throught the Regular / BackToFront and FrontToBack lists.
        /// </summary>
        /// <param name="camPos">Camera position.</param>
        /// <param name="collider">Bounding sphere.</param>
        public void Process(Vector3 camPos, BoundingSphere collider)
        {
            m_regularList.Clear();
            m_backToFrontList.Clear();
            m_frontToBackList.Clear();
            m_addedItemsIds.Clear();
            m_root.Process(camPos, collider);
        }

    #endregion

    }
}
